/*
 * GameBoard.java
 *
 * Created on November 22, 2007, 6:50 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.UI;

import hextd.HexTD;
import hextd.creep.Creep;
import hextd.tower.Tower;
import hextd.core.Context;
import hextd.core.ContextNormal;
import hextd.core.GameGrid;
import hextd.hex.Hex;
import hextd.util.RunningAverage;
import hextd.wave.Path;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 *
 * @author  hylke
 */
public class GameBoard extends javax.swing.JPanel implements ScaledCanvas {

	private HexTD game;
	private ContextNormal context;
	private final Context.GameTime gameTime;
	//
	private int boardWidth, boardHeight;
	private float offsetX, offsetY;
	private float paintScale, strokeScale;
	private float hexPaintWidth;
	private boolean leftToRight = true;
	//
	private BufferedImage layer0; // Cache image for the background
	private BufferedImage layer1; // Cache image for the hexes
	private boolean clearLayer0 = true;
	private boolean clearLayer1 = true;
	private boolean drawBackground = true;
	private boolean qualityHigh = true;
	//
	private Timer paintTimer;
	private TimerTask paintTask;
	private int paintDelayFast = 50;
	private int paintDelaySlow = 250;
	private int paintDelay = paintDelayFast;
	//
	private boolean reScale = true;
	private boolean autoQuality = true;
	private RunningAverage paintSpeedSlow;
	private RunningAverage paintSpeedFast;
	private RunningAverage paintSpeed;
	//
	private static final float wFloat = (float) Math.sin(Math.PI / 3.0);

	/**
	 * Creates new form GameBoard
	 */
	public GameBoard(HexTD game, ContextNormal context) {
		this.game = game;
		this.context = context;
		gameTime = context.getGameTime();
		paintSpeedSlow = new RunningAverage(20, 1);
		paintSpeedFast = new RunningAverage(20, 0);
		paintSpeed = new RunningAverage(20, 0);
		initComponents();
	}

	@Override
	public void paintComponent(Graphics g) {
		this.paintBoard((Graphics2D) g);
	}

	public void paintBoard(Graphics2D g2) {
		if (paintTask == null) {
			g2.setColor(Color.black);
			g2.fillRect(0, 0, getWidth(), getHeight());
		} else {
			long timeStart = new Date().getTime();
			GameGrid grid = context.getGameGrid();
			if (grid == null) {
				g2.setColor(Color.black);
				g2.fillRect(0, 0, getWidth(), getHeight());
				return;
			}

			if (this.reScale) {
				this.reScale = false;
				this.doRecalculateScale();
			}

			if (this.layer0 == null) {
				return;
			}
			int time;
			// Two layers for the background (hexes) that are cached.
			Graphics2D g2_0 = null;
			Graphics2D g2_1 = null;
			boolean clearedLayer0 = false;
			boolean clearedLayer1 = false;

			time = gameTime.getTime();

			if (clearLayer0 || clearLayer1) {
				//System.out.println("Redrawing layer 0...");
				clearLayer0 = false;
				clearedLayer0 = true;
				g2_0 = layer0.createGraphics();
				g2_0.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				g2_0.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
				g2_0.setColor(this.context.colors.getColorBaseBackGround());
				g2_0.fillRect(0, 0, this.boardWidth, this.boardHeight);

				if (drawBackground) {
					BufferedImage bg;
					if (context.getBackgroundImage(true).length() > 0) {
						bg = context.loadImage(context.getBackgroundImage(true));

						int width = Math.round(boardWidth - offsetX * 2);
						int height = Math.round(boardHeight - offsetY * 2);
						int ox = Math.round((boardWidth - width) / 2);
						int oy = Math.round((boardHeight - height) / 2);
						g2_0.drawImage(bg, ox, oy, width, height, context.colors.getColorBaseBackGround(), null);
					}
				}
			}

			if (clearLayer1) {
				//System.out.println("Redrawing layer 1...");
				clearLayer1 = false;
				clearedLayer1 = true;
				g2_1 = this.layer1.createGraphics();
				g2_1.setComposite(AlphaComposite.Clear);
				g2_1.fillRect(0, 0, this.boardWidth, this.boardHeight);
				g2_1.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				g2_1.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
				g2_1.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
			}

			if (clearedLayer0 || clearedLayer1) {
				if (!clearedLayer0) {
					g2_0 = this.layer0.createGraphics();
					g2_0.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
					g2_0.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
				}
				if (grid != null && grid.grid != null) {
					for (Hex[] row : grid.grid) {
						for (Hex h : row) {
							if (h != null) {
								h.paintBase(g2_0, g2_1);
							}
						}
					}
				}
				g2_0.drawImage(this.layer1, 0, 0, null);
			}
			if (g2_0 != null) {
				g2_0.dispose();
			}
			if (g2_1 != null) {
				g2_1.dispose();
			}

			setRenderHints(g2);
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

			// Draw the cached background.
			g2.drawImage(this.layer0, 0, 0, null);

			if (grid != null && grid.grid != null) {
				for (Hex[] row : grid.grid) {
					for (Hex h : row) {
						if (h != null) {
							h.paintEffects(g2);
						}
					}
				}
			}
			if (qualityHigh) {
				ArrayList<Path> paths = this.context.getPaths();
				for (int i = paths.size() - 1; i >= 0; i--) {
					paths.get(i).paint(g2, time);
				}
			}
			try {
				for (Creep e : this.context.getEnemies()) {
					e.paint(g2, time);
				}
			} catch (ConcurrentModificationException e) {
			}
			try {
				for (Tower t : this.context.getTowers()) {
					t.paint(g2, time);
				}

				for (Tower t : this.context.getTowers()) {
					t.paintEffects(g2, time);
				}
			} catch (ConcurrentModificationException e) {
			}

			long timeEnd = new Date().getTime();
			long paintDuration = timeEnd - timeStart;
			float avg = paintSpeed.put((int) paintDuration);
			float speed = 0;
			if (autoQuality) {
				if (qualityHigh) {
					boolean slow = (paintDuration > 40);
					if (slow) {
						speed = paintSpeedSlow.put(0);
					} else {
						speed = paintSpeedSlow.put(1);
					}
					if (speed < 0.2) {
						System.out.println("Gameboard: Slow in painting: " + speed);
						System.out.println("Gameboard: Setting low quality");
						game.setQuality(false);
						paintSpeedSlow.reset(1);
						paintSpeedFast.reset(0);
					}
				} else {
					boolean fast = (paintDuration < 10);
					if (fast) {
						speed = paintSpeedFast.put(1);
					} else {
						speed = paintSpeedFast.put(0);
					}
					if (speed > 0.8) {
						System.out.println("Gameboard: Fast in painting: " + speed);
						System.out.println("Gameboard: Setting high quality");
						game.setQuality(true);
						paintSpeedSlow.reset(1);
						paintSpeedFast.reset(0);
					}
				}
			}
			game.setFps(Math.min(20f, 1000f / avg));
		}
	}

	private void setRenderHints(Graphics2D g2) {
		if (this.qualityHigh) {
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
		} else {
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
			g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
		}

	}

	public Hex hexFromScreenCoords(int screenx, int screeny) {
		int row, col;
		float x, y, dx, dy, h, r, hWidth, hHeight;

		hWidth = this.getHexPaintWidth() * 2;
		hHeight = this.paintScale * 1.5f;
		h = this.paintScale / 2;
		r = this.getHexPaintWidth();
		x = screenx - this.offsetX;
		y = screeny - this.offsetY;

		if (leftToRight) {
			col = Math.round(-0.5f + x / hWidth);
			row = Math.round(-0.5f + y / hHeight);
			dx = x - col * hWidth;
			dy = y - row * hHeight;

			if ((row & 1) == 0) {
				if (dy < (h - (h / r) * dx)) {
					col--;
					row--;
				}

				if (dy < (-h + (h / r) * dx)) {
					row--;
				}

			} else {
				if (dx > r) {
					if (dy < h * 2 - h / r * dx) {
						row--;
					}
				} else {
					if (dy < h / r * dx) {
						row--;
					} else {
						col--;
					}
				}
			}
		} else {
			col = Math.round(-0.5f + x / hHeight);
			row = Math.round(-0.5f + y / hWidth);
			dx = x - col * hHeight;
			dy = y - row * hWidth;

			if ((col & 1) == 0) {
				if (dx < (h - (h / r) * dy)) {
					col--;
					row--;
				}

				if (dx < (-h + (h / r) * dy)) {
					col--;
				}

			} else {
				if (dy > r) {
					if (dx < h * 2 - h / r * dy) {
						col--;
					}
				} else {
					if (dx < h / r * dy) {
						col--;
					} else {
						row--;
					}
				}
			}
		}
		GameGrid gameGrid = context.getGameGrid();
		if (gameGrid != null) {
			return gameGrid.getHex(col, row);
		} else {
			return null;
		}
	}

	public void hexChanged() {
		this.clearLayer1 = true;
	}

	public void recalculateScale() {
		this.reScale = true;
	}

	public void doRecalculateScale() {
		float scale;
		GameGrid grid = context.getGameGrid();
		boardWidth = getWidth();
		boardHeight = getHeight();

		if (grid != null && this.boardWidth > 0 && this.boardHeight > 0 && context.getGameResult() != HexTD.GameResult.loading) {
			float floatGridWidth, floatGridHeight;
			int gridWidth = grid.getGridWidth();
			int gridHeight = grid.getGridHeight();
			leftToRight = grid.isLeftToRight();

			this.context.getCache().clearCache();

			layer0 = new BufferedImage(this.boardWidth, this.boardHeight, BufferedImage.TYPE_INT_RGB);
			layer1 = new BufferedImage(this.boardWidth, this.boardHeight, BufferedImage.TYPE_INT_ARGB_PRE);

			float xScale, yScale;
			if (leftToRight) {
				floatGridWidth = 2f * (wFloat) * gridWidth;
				floatGridHeight = 1.5f * gridHeight + 0.5f;
				xScale = (boardWidth - 2) / (floatGridWidth);
				yScale = (boardHeight - 2) / (floatGridHeight);
			} else {
				floatGridWidth = 1.5f * gridWidth + 0.5f;
				floatGridHeight = 2f * (wFloat) * gridHeight;
				xScale = (boardWidth - 2) / floatGridWidth;
				yScale = (boardHeight - 2) / floatGridHeight;
			}

			scale = Math.min(xScale, yScale);
			paintScale = scale;
			strokeScale = scale / 40f;
			setHexPaintWidth(wFloat * scale);

			offsetX = (boardWidth - scale * floatGridWidth) / 2;
			offsetY = (boardHeight - scale * floatGridHeight) / 2;

			if (grid.grid != null) {
				for (Hex[] row : grid.grid) {
					for (Hex h : row) {
						if (h != null) {
							h.resetScale();
						}

					}
				}
			}
			try {
				for (Creep e : this.context.getEnemies()) {
					e.resetScale();
				}

				for (Tower t : this.context.getTowers()) {
					t.resetScale();
				}

				ArrayList<Path> paths = this.context.getPaths();
				for (Path p : paths) {
					p.resetScale();
				}

			} catch (ConcurrentModificationException e) {
			}
			this.clearLayer0 = true;
			this.clearLayer1 = true;
		}

	}

	public synchronized void setSlow(boolean slow) {
		if (slow && paintDelay != paintDelaySlow) {
			paintDelay = paintDelaySlow;
			if (paintTask != null) {
				stopPaintTimer();
				startPaintTimer();
			}
		} else if (!slow && paintDelay != paintDelayFast) {
			paintDelay = paintDelayFast;
			if (paintTask != null) {
				stopPaintTimer();
				startPaintTimer();
			}
		}
	}

	public synchronized void startPaintTimer() {
		if (this.paintTimer == null) {
			this.paintTimer = new Timer("gameBoardPaintTimer", true);
		}

		if (this.paintTask == null) {
			this.paintTask = new TimerTask() {

				@Override
				public void run() {
					repaint();
				}
			};
			this.paintTimer.schedule(paintTask, paintDelay, paintDelay);
		}

	}

	public synchronized void stopPaintTimer() {
		if (this.paintTask != null) {
			this.paintTask.cancel();
			this.paintTask = null;
		}

	}

	@Override
	public float getPaintScale() {
		return paintScale;
	}

	@Override
	public void setPaintScale(float scale) {
		paintScale = scale;
	}

	@Override
	public float getStrokeScale() {
		return strokeScale;
	}

	@Override
	public void setStrokeScale(float scale) {
		strokeScale = scale;
	}

	public void cleanup() {
		if (paintTimer != null) {
			paintTimer.cancel();
			paintTimer =
					null;
		}

		paintTask = null;
		if (layer0 != null) {
			layer0.flush();
			layer0 =
					null;
		}

		if (layer1 != null) {
			layer1.flush();
			layer1 =
					null;
		}

		game = null;
		context =
				null;
	}

	@Override
	public void setVisible(boolean aFlag) {
		super.setVisible(aFlag);
		repaint();
	}

	/** This method is called from within the constructor to
	 * initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is
	 * always regenerated by the Form Editor.
	 */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        addComponentListener(new java.awt.event.ComponentAdapter() {
            public void componentResized(java.awt.event.ComponentEvent evt) {
                formComponentResized(evt);
            }
        });
        addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyTyped(java.awt.event.KeyEvent evt) {
                formKeyTyped(evt);
            }
        });
        setLayout(null);
    }// </editor-fold>//GEN-END:initComponents
    private void formKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyTyped
		this.game.keyTyped(evt.getKeyChar());
    }//GEN-LAST:event_formKeyTyped

    private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
		this.recalculateScale();
    }//GEN-LAST:event_formComponentResized
    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables

	public boolean isDrawBackground() {
		return drawBackground;
	}

	public void setDrawBackground(boolean drawBackground) {
		this.drawBackground = drawBackground;
		this.clearLayer0 = true;
	}

	public boolean isQualityHigh() {
		return qualityHigh;
	}

	public void setQualityHigh(boolean qualityHigh) {
		this.qualityHigh = qualityHigh;
	}

	@Override
	public float getOffsetX() {
		return offsetX;
	}

	@Override
	public void setOffsetX(float offsetX) {
		this.offsetX = offsetX;
	}

	@Override
	public float getOffsetY() {
		return offsetY;
	}

	@Override
	public void setOffsetY(float offsetY) {
		this.offsetY = offsetY;
	}

	@Override
	public float getHexPaintWidth() {
		return hexPaintWidth;
	}

	@Override
	public void setHexPaintWidth(float hexPaintWidth) {
		this.hexPaintWidth = hexPaintWidth;
	}

	/**
	 * @return the autoQuality
	 */
	public boolean isAutoQuality() {
		return autoQuality;
	}

	/**
	 * @param autoQuality the autoQuality to set
	 */
	public void setAutoQuality(boolean autoQuality) {
		this.autoQuality = autoQuality;
	}
}
